package com.dragon.client.config.security;


import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.dragon.common.constant.ContextConfig;
import com.dragon.common.constant.PlatformConstant;
import com.dragon.common.model.ResponseData;
import com.dragon.common.model.ResultCode;
import com.dragon.common.util.JwtUtil;
import com.dragon.common.util.RedisUtil;
import com.dragon.logical.service.auth.AdminService;
import com.dragon.repository.entity.Admin;
import com.dragon.repository.entity.AdminToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.dragon.common.util.JwtUtil.TOKEN_PREFIX;


public class JWTAhenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    AdminService adminService;

    @Autowired
    RedisUtil redisUtil;

    public JWTAhenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtUtil.HEADER_STRING);
        if (tokenHeader == null || !tokenHeader.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        String token = JwtUtil.validateToken(tokenHeader);
        if (token == null) {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(403);
            ResponseData fail = ResponseData.error(ResultCode.TOKEN_ERROR.code(),ResultCode.TOKEN_ERROR.message());
            response.getWriter().write(JSON.toJSONString(fail));
            return;
        }

        Map map = JSON.parseObject(token, Map.class);
        String userId = MapUtil.get(map, "id", String.class);
        PlatformConstant platformConstant = MapUtil.get(map,"platformConstant",PlatformConstant.class);
        if (StrUtil.isBlank(userId)) {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(403);
            ResponseData fail = ResponseData.error(ResultCode.TOKEN_ERROR.code(),ResultCode.TOKEN_ERROR.message());
            response.getWriter().write(JSON.toJSONString(fail));
            return;
        }

        AdminToken userTokens = JSON.parseObject(token, AdminToken.class);


        if (StrUtil.isBlank(String.valueOf(userTokens.getId()))) {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(403);
            ResponseData fail = ResponseData.error(ResultCode.TOKEN_ERROR.code(),ResultCode.TOKEN_ERROR.message());
            response.getWriter().write(JSON.toJSONString(fail));
            return;
        }

        if (LocalDateTime.now().isAfter(userTokens.getExpiresAt())) {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(401);
            response.setHeader(JwtUtil.HEADER_STRING, TOKEN_PREFIX.concat(refreshToken(userTokens,true)));
            ResponseData fail = ResponseData.error(ResultCode.TOKEN_EXPIRESS_ERROR.code(),ResultCode.TOKEN_EXPIRESS_ERROR.message());
            response.getWriter().write(JSON.toJSONString(fail));
            return;
        }

        AdminToken adminToken = (AdminToken) redisUtil.get(adminService.generateKey(userTokens.getId(),userTokens.getPlatformConstant()));
        String md5Str = DigestUtil.md5Hex(tokenHeader.replace(TOKEN_PREFIX,""));

        if(!md5Str.equals(adminToken.getMd5TokenStr())){
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(403);
            ResponseData fail = ResponseData.error(ResultCode.TOKEN_IS_LOGIN_ERROR.code(),ResultCode.TOKEN_IS_LOGIN_ERROR.message());
            response.getWriter().write(JSON.toJSONString(fail));
            return;
        }

        userTokens.setMd5TokenStr(md5Str);

        refreshToken(userTokens,false);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userTokens.getId(), null, AuthorityUtils.commaSeparatedStringToAuthorityList("system"));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    /**
     * 刷新token，并返回token
     * @param userTokens
     * @param isCreateToken
     * @return
     */
    private String refreshToken(AdminToken userTokens,Boolean isCreateToken){
        String token = "";
        LocalDateTime issueAt = LocalDateTime.now();
        LocalDateTime expireAt = issueAt.plusSeconds(userTokens.getRemember() ? TimeUnit.DAYS.toSeconds(JwtUtil.REDIS_EXPIRE_DAY) : TimeUnit.MINUTES.toSeconds(JwtUtil.REDIS_EXPIRE_MINUTES));
        int expireSeconds = (int) Duration.between(issueAt, expireAt).getSeconds();
        //刷新token有效期
        AdminToken adminToken = new AdminToken();
        adminToken.setId(userTokens.getId());
        adminToken.setUsername(userTokens.getUsername());
        adminToken.setDepartmentSet(userTokens.getDepartmentSet());
        adminToken.setIsAdmin(userTokens.getIsAdmin());
        adminToken.setAuthCode(userTokens.getAuthCode());
        adminToken.setExpiresAt(expireAt);
        adminToken.setRemember(userTokens.getRemember());
        adminToken.setMd5TokenStr(userTokens.getMd5TokenStr());
        if(isCreateToken){
            token = JwtUtil.generateToken(JSONUtil.toJsonStr(adminToken));
            adminToken.setMd5TokenStr(token);
        }
        //存入redis中
        redisUtil.set(adminService.generateKey(userTokens.getId(),userTokens.getPlatformConstant()), adminToken, expireSeconds);
        return token;
    }

}
